package com.googlecode.pathmachine.ui;

import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.input.InputManager;
import com.jme3.input.MouseInput;
import com.jme3.input.RawInputListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import java.io.IOException;

/**
 *
 * @author eadams
 */
public class RTSCamera implements Control, AnalogListener, RawInputListener {

    private float minVerticalRotation = FastMath.PI / 12f;
    private float maxVerticalRotation = FastMath.PI / 4;
    private float distance = 10f;
    protected float vRotation = maxVerticalRotation;
    protected boolean vRotating = false;
    protected InputManager inputManager;
    protected Vector3f initialUpVec;
    private float rotationSensitivity = 2f;
    private float rotationSpeed = 1f;
    private float moveSpeed = 10f;
    protected float targetVRotation = vRotation;
    protected float vRotationLerpFactor = 0;
    protected boolean enabled = true;
    protected Camera cam = null;
    protected final Vector3f targetDir = new Vector3f();
    protected final Vector3f pos = new Vector3f();
    protected Vector3f targetLocation = new Vector3f(0, 0, 0);
    protected final static String RTSCamZoomIn = "RTSCamZoomIn";
    protected final static String RTSCamZoomOut = "RTSCamZoomOut";
    private int maxXCursor;
    private int minYCursor = 2;
    private int maxYCursor;
    private int minXCursor = 2;
    protected float x, y;
    protected Vector3f localTranslation = new Vector3f();
    protected float rotation = 0;

    public RTSCamera(Camera camera, Vector3f loc, InputManager inputManager, int screenWidth, int screenHeight) {
        this.cam = camera;
        initialUpVec = cam.getUp().clone();
        localTranslation.set(loc);
        maxXCursor = screenWidth - 2;
        maxYCursor = screenHeight - 2;
        this.x = maxXCursor / 2;
        this.y = maxYCursor / 2;
        registerWithInput(inputManager);
    }

    /**
     * Registers inputs with the input manager
     * @param inputManager
     */
    private void registerWithInput(InputManager inputManager) {

        String[] inputs = {
            RTSCamZoomIn,
            RTSCamZoomOut
        };

        this.inputManager = inputManager;
        inputManager.addMapping(RTSCamZoomIn, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
        inputManager.addMapping(RTSCamZoomOut, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
        inputManager.addListener(this, inputs);
        inputManager.addRawInputListener(this);
    }

    private void updateCamera(float tpf) {
        if (!enabled) {
            return;
        }

        if (y >= maxYCursor) {
            localTranslation.addLocal(0f, 0f, -moveSpeed * tpf);
        }

        if (y <= minYCursor) {
            localTranslation.addLocal(0f, 0f, moveSpeed * tpf);
        }

        if (x >= maxXCursor) {
            localTranslation.addLocal(moveSpeed * tpf, 0f, 0f);
        }

        if (x <= minXCursor) {
            localTranslation.addLocal(-moveSpeed * tpf, 0f, 0f);
        }

        targetLocation.set(localTranslation);

        //linear interpolation of the distance while vRotating
        if (vRotating) {
            vRotationLerpFactor = Math.min(vRotationLerpFactor + tpf * tpf * rotationSensitivity, 1);
            vRotation = FastMath.interpolateLinear(vRotationLerpFactor, vRotation, targetVRotation);
            if (targetVRotation + 0.01f >= vRotation && targetVRotation - 0.01f <= vRotation) {
                vRotating = false;
                vRotationLerpFactor = 0;
            }
        }

        //computing the position
        computePosition();
        //setting the position at last
        cam.setLocation(pos);

        //the cam looks at the target            
        cam.lookAt(targetLocation, initialUpVec);
    }

    private void vRotateCamera(float value) {
        if (!enabled) {
            return;
        }

        vRotating = true;

        targetVRotation += value * rotationSpeed;
        if (targetVRotation > maxVerticalRotation) {
            targetVRotation = maxVerticalRotation;
        }

        if (targetVRotation < minVerticalRotation) {
            targetVRotation = minVerticalRotation;
        }
    }

    private void computePosition() {
        float hDistance = (distance) * FastMath.sin((FastMath.PI / 2) - vRotation);
        pos.set(hDistance * FastMath.sin(rotation), (distance) * FastMath.sin(vRotation), hDistance * FastMath.cos(rotation));
        pos.addLocal(localTranslation);
    }

    public void onAnalog(String name, float value, float tpf) {
        if (name.equals(RTSCamZoomIn)) {
            vRotateCamera(-value);
        } else if (name.equals(RTSCamZoomOut)) {
            vRotateCamera(+value);
        }
    }

    public Control cloneForSpatial(Spatial spatial) {
        RTSCamera c = new RTSCamera(cam, spatial.getLocalTranslation(), inputManager, maxXCursor + 2, maxYCursor + 2);
        return c;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public boolean isEnabled() {
        return this.enabled;
    }

    public void update(float tpf) {
        updateCamera(tpf);
    }

    public void render(RenderManager rm, ViewPort vp) {
        // do nothing
    }

    public void write(JmeExporter ex) throws IOException {
        //do nothing
    }

    public void read(JmeImporter im) throws IOException {
        // do nothing
    }

    public void setSpatial(Spatial spatial) {
        if (spatial == null) {
            return;
        }
        localTranslation.set(spatial.getLocalTranslation());
    }

    public void beginInput() {
        // do nothing
    }

    public void endInput() {
        // do nothing
    }

    public void onJoyAxisEvent(JoyAxisEvent evt) {
        // do nothing
    }

    public void onJoyButtonEvent(JoyButtonEvent evt) {
        // do nothing
    }

    public void onMouseMotionEvent(MouseMotionEvent evt) {
        x = evt.getX();
        y = evt.getY();
    }

    public void onMouseButtonEvent(MouseButtonEvent evt) {
        // do nothing
    }

    public void onKeyEvent(KeyInputEvent evt) {
        // do nothing
    }

    /**
     * @param minVerticalRotation the minVerticalRotation to set
     */
    public void setMinVerticalRotation(float minVerticalRotation) {
        this.minVerticalRotation = minVerticalRotation;
    }

    /**
     * @param maxVerticalRotation the maxVerticalRotation to set
     */
    public void setMaxVerticalRotation(float maxVerticalRotation) {
        this.maxVerticalRotation = maxVerticalRotation;
        vRotation = maxVerticalRotation;
    }

    /**
     * @param distance the distance to set
     */
    public void setDistance(float distance) {
        this.distance = distance;
    }

    /**
     * @param rotationSensitivity the rotationSensitivity to set
     */
    public void setRotationSensitivity(float rotationSensitivity) {
        this.rotationSensitivity = rotationSensitivity;
    }

    /**
     * @param rotationSpeed the rotationSpeed to set
     */
    public void setRotationSpeed(float rotationSpeed) {
        this.rotationSpeed = rotationSpeed;
    }

    /**
     * @param moveSpeed the moveSpeed to set
     */
    public void setMoveSpeed(float moveSpeed) {
        this.moveSpeed = moveSpeed;
    }
}
